// =============================================
// FD Displace
// Made by Ubik and Claude 2026
// =============================================
// GPU displacement: offsets source video pixels
// using a displacement map video.
// Source video -> video in 1 (tex0)
// Displace map -> video in 2 (tex1)
//
// amount   - Displacement strength
// angle    - Direction in degrees (1D mode)
// offset   - Brightness offset for displace map
//            Grey (0.5) = no displacement
// use_xy   - >0.5: R drives X, G drives Y (2D)
// do_wrap  - >0.5: pixels wrap around edges
//            <0.5: out-of-bounds = see src_bkg
// src_bkg  - >0.5: original source fills OOB
//            <0.5: transparent alpha for OOB
// softness - Fade displaced pixels near edges
// =============================================

// ISADORA_PLUGIN_DESC("Displace - displaces source video using a displacement map. Source to in 1, map to in 2.")

// ISADORA_FLOAT_PARAM(amount, amnt, 0.0, 1.0, 0.1, "Displacement strength. 0 = no displacement, 1 = maximum.")
// ISADORA_FLOAT_PARAM(angle, angl, 0.0, 360.0, 0.0, "Displacement direction in degrees (1D mode only, when use_xy is off).")
// ISADORA_FLOAT_PARAM(offset, ofst, -0.5, 0.5, 0.0, "Brightness offset for the displacement map. Grey = no displacement.")
// ISADORA_FLOAT_PARAM(use_xy, uxy_, 0.0, 1.0, 0.0, "2D mode when > 0.5: R channel drives X, G channel drives Y.")
// ISADORA_FLOAT_PARAM(do_wrap, wrap, 0.0, 1.0, 1.0, "Wrap pixels around edges when > 0.5. Off = out-of-bounds handled by src_bkg.")
// ISADORA_FLOAT_PARAM(src_bkg, sbkg, 0.0, 1.0, 0.0, "When wrap is off: > 0.5 = original source fills out-of-bounds. < 0.5 = transparent.")
// ISADORA_FLOAT_PARAM(softness, soft, 0.0, 1.0, 0.0, "Edge softness - fades displaced pixels near edges (wrap off only).")

uniform float amount;
uniform float angle;
uniform float offset;
uniform float use_xy;
uniform float do_wrap;
uniform float src_bkg;
uniform float softness;
uniform vec3 iResolution;
uniform sampler2D tex0;
uniform sampler2D tex1;

void main()
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

    vec4 dispSample = texture2D(tex1, uv);
    vec3 dRGB = dispSample.rgb;
    float dLuma = dot(dRGB, vec3(0.299, 0.587, 0.114));

    // 1D mode: displace along angle using luminance
    float displacement = (dLuma - 0.5 + offset) * amount * 2.0;
    float rad = radians(angle);
    vec2 dispVec1D = vec2(cos(rad), sin(rad)) * displacement;

    // 2D mode: R drives X, G drives Y
    float dispX2D = (dRGB.r - 0.5 + offset) * amount * 2.0;
    float dispY2D = (dRGB.g - 0.5 + offset) * amount * 2.0;
    vec2 dispVec2D = vec2(dispX2D, dispY2D);

    // Blend between 1D and 2D based on use_xy
    float mode2d = step(0.5, use_xy);
    vec2 displaceVec = mix(dispVec1D, dispVec2D, mode2d);

    vec2 newUV = uv + displaceVec;

    // Out-of-bounds detection on raw displaced UV
    float oobX = step(1.0, newUV.x) + step(newUV.x, 0.0);
    float oobY = step(1.0, newUV.y) + step(newUV.y, 0.0);
    float isOOB = clamp(oobX + oobY, 0.0, 1.0);

    // Always wrap for safe GPU sampling - avoids edge pixel stretch
    // OOB is handled via alpha/color below, not via clamp
    float wrapFactor = step(0.5, do_wrap);
    vec2 sampledUV = fract(newUV + 1.0);

    vec4 displaced = texture2D(tex0, sampledUV);
    vec4 sourcePixel = texture2D(tex0, uv);

    // When wrap is off and pixel is OOB:
    //   src_bkg > 0.5 -> show original source pixel
    //   src_bkg < 0.5 -> transparent (alpha = 0)
    float isOOBActive = isOOB * (1.0 - wrapFactor);
    vec4 oobFill = mix(
        vec4(0.0, 0.0, 0.0, 0.0),
        vec4(sourcePixel.rgb, sourcePixel.a),
        step(0.5, src_bkg)
    );
    vec4 result = mix(displaced, oobFill, isOOBActive);

    // Edge softness: fade to transparent (or source) near edges, wrap off only
    float edgeDist = min(min(sampledUV.x, 1.0 - sampledUV.x),
                         min(sampledUV.y, 1.0 - sampledUV.y));
    float edgeFade = smoothstep(0.0, softness * 0.15 + 0.0001, edgeDist);
    float softApply = softness * (1.0 - wrapFactor);
    vec4 softFill = mix(vec4(0.0, 0.0, 0.0, 0.0), sourcePixel, step(0.5, src_bkg));
    result = mix(result, mix(result, softFill, 1.0 - edgeFade), softApply);

    gl_FragColor = result;
}
